#
# "@(#) $Id: LXinePlayer.py,v 1.1.1.1 2006-07-27 03:16:08 gioshe Exp $"
#
# This work is released under the GNU GPL, version 2 or later.
#
from qt import *
import pyxine
import pyxine.config
import time,re,random,traceback
from Utils.Settings import *
import warnings
from Utils.guessgenre import *
from CD.Ripper import *


_curPlayerThread = None # the singleton thread for the current player
_altPlayerthread = None # used to keep extra reference to avoid being garbage collected too early
_playerMutex = QMutex() # used to pass info between threads

_playerCommand = ""
_playerResponse = ""
_playerStatus = None
_seekTime = 0
_currentPlayerTrack = None
_currentPlayerLibrary = None
_currentPlayerPlaylist = None
_currentPlayerVolume = 100
_currentPlayerSpeed = 100
_currentEQ = None
_eqMultiplier = 10
_eqOffset = 0
_canOverride = True
_currentPlaylists = {}
_played = False
_parent=None

_xine = None
_xineConfig = None
_nextCDPlayer = "/dev/cdrom"
_prevCDPlayer = _nextCDPlayer
_stream = None

warnings.filterwarnings("error",category=UserWarning)

def prev(a,b):
	a = a-1
	if a<0: a = b-1
	return a

def succ(a,b):
	a = a+1
	if a==b: a = 0
	return a

#
# a custom event used by the music player thread to communicate
# with the main thread - it's usually posted to the singleton instance of
# LXinePlayer
#
class LXinePlayerEvent(QCustomEvent):
	def __init__(self,status = None):
		QCustomEvent.__init__(self,QEvent.User+1)
		self._playerStatus = status
	
	def status(self):
		return self._playerStatus

#
# this abstract class represents a thread used to play music
# and is subclassed for a particular implementation of a player
#
class LXineObserver(QThread):
	def __init__(self,track):
		QThread.__init__(self)
		global _altPlayerThread
		_altPlayerThread = self
		self.track = track
		self.isPlaying = True
		self.mute = False
		self.lastTime = long(time.time()*5)
		
	def run(self):
		status = self.getStatus()
		#status['Status'] = 'Begin'
		#self.emitEvent(status)
		userInterrupted = False
		while 1:
			cmd = self.getCommand()
			if cmd=='stop':
				userInterrupted = True
				break;
			elif cmd=='seek':
				self.doSeek()
			elif cmd=='play':
				self.play()
				self.putResponse('playing')
			elif cmd=='pause':
				self.pause()
				self.putResponse('paused')
			elif cmd=='volume':
				self.setVolume(_currentPlayerVolume)
			elif cmd=='speed':
				self.setSpeed(_currentPlayerSpeed)
			elif cmd=='eq':
				self.setEQ(_currentEQ)
			if self.step():
				#self.emitEvent(self.getStatus())
				self.isPlaying = False
				break
			self.maybeEmitEvent(self.getStatus())
		status = self.getStatus()
		status['Status'] = 'End'
		status['Interrupted'] = userInterrupted
		self.emitEvent(status)
		self.doDie()

	def getStatus(self):
		(currentTime,totalTime) = self.getPos()
		status = {'Source':'Player','Status':'Playing','Track':self.track,'currentTime':currentTime,'totalTime':totalTime,'isPlaying':self.isPlaying,'Interrupted':False}
		return status
	
	#
	# override to so a slice of time for this player
	# return True if the track has completed playing
	#
	def step(self):	return True
	def play(self):	self.isPlaying = True
	def pause(self): self.isPlaying = False
	def setVolume(self,volume): pass
	def setSpeed(self,speed): pass 
	def setEQ(self,eq):	pass
	# override this to report progress
	# return a tuple of (currentTime, totalTime)
	def getPos(self):	return (0,0)
	# override to seek the track to a particular offset
	def setPos(self):	pass
	def doSeek(self):
		global _seekTime
		self.setPos(_seekTime)
		self.putResponse("seeked")
	def doDie(self):
		#print "doDie"
		global _curPlayerThread
		global _curPlayerTrack
		self.die()
		global _playerMutex
		_playerMutex.lock()
		_curPlayerThread = None
		#_currentPlayerTrack = None
		self.playing = False
		_playerMutex.unlock()
		self.putResponse('stopped')

	# override to close a stream, either because it
	# has completed playing, or because the user interrupted
	def die(self): pass

	def getCommand(self):
		global _playerCommand
		global _playerMutex
		_playerMutex.lock()
		cmd = _playerCommand
		_playerCommand = ''
		_playerMutex.unlock()
		return cmd

	def putResponse(self,resp):
		global _playerResponse
		global _playerMutex
		_playerMutex.lock()
		_playerResponse = resp
		_playerMutex.unlock()

		#
		# throttle the rate of non-transition status notifications
		# to 5 per sec
		#
	def maybeEmitEvent(self,status):
		now = long(time.time()*5)
		if now!=self.lastTime:
			self.emitEvent(status)
			self.lastTime = now

	def emitEvent(self,status):
		event = LXinePlayerEvent(status)
		QApplication.postEvent(_parent,event)

def cleanPath(filePath): return re.sub(r"#",r"%23",filePath)
#
# a subclass of LMusicThread specifically tied into Xine
#
class LXineEngine(QThread):
	def __init__(self,prefs):
		QThread.__init__(self)
		xine=pyxine.Xine(prefs)
		#global _altPlayerThread
		#_altPlayerThread = self
		#self.track = track
		#self.isPlaying = True
		#self.mute = False
		self.lastTime = long(time.time()*5)
		
	def pause(self):
		global _stream
		_stream.speed = 'XINE_SPEED_PAUSE'
		#_stream.setSpeed(0)
		LXineObserver.pause(self)
	
	def play(self):
		global _stream
		#_stream.speed = 'XINE_SPEED_FAST_2'
		_stream.speed = 'XINE_SPEED_NORMAL'
		#_stream.setSpeed(_currentPlayerSpeed)
		LXineObserver.play(self)

	def step(self):
		global _stream
		self.msleep(100)
		status = _stream.get_status()
		done = status!='XINE_STATUS_PLAY'
		return done
		
	def setVolume(self,volume):
		global _stream
		_stream.audio_volume = volume
	
	def setSpeed(self,speed):
		global _stream
		newSpeed = long(1000000L*(speed/100.0))
		print "newSpeed",newSpeed
		pyxine.libxine.xine_set_param(_stream.this,30,newSpeed)

	def setEQ(self,eq):
		try:
			if eq==None or not eq['On']:
				self.setBands([0,0,0,0,0,0,0,0,0,0])
			else:
				self.setBands(eq['Bands'])
		except: pass

	def setBands(self,bands):
		global _stream,_eqMultiplier,_eqOffset
		for i in xrange(0,10):
			pyxine.libxine.xine_set_param(_stream.this,18+i,_eqMultiplier*bands[i]+_eqOffset)

	def getPos(self):
		global _stream
		if self.opened:
			try:
				(currentPosition,currentTime,totalTime) = _stream.get_pos_length()
				if totalTime:
				#	currentTime = currentTime*self.track.totalTime/totalTime/1000
					totalTime = self.track.totalTime/1000.0
					currentTime = currentPosition*totalTime/100.0
				#print currentPosition,currentTime,totalTime
			except:
				currentTime = 0
				totalTime = 1
			return (currentTime*1000,totalTime*1000)
		else:
			return (0,100)

	def setPos(self,time):
		global _stream
		(currentPosition,currentTime,totalTime) = _stream.get_pos_length()
		time = time*totalTime*1000/self.track.totalTime
		_stream.play(start_time=(time/1000.0))

class LXinePlayerThread(LXineObserver):
	def __init__(self,track):
		global _currentPlayerVolume,_stream,_currentEQ
		LXineObserver.__init__(self,track)
		#xine = LXinePlayer.xine(self)
		if _stream==None:
			_stream = _xine.stream_new()
			print "built",_stream
		self.setVolume(_currentPlayerVolume)
		#self.setSpeed(_currentPlayerSpeed)
		self.setEQ(_currentEQ)
		self.opened = False
	
	def open(self):
		global _stream
		#try:
		#print "attempting to open",self.track.location," and stream ", _stream, "_parent is ", _parent
		status = self.getStatus()
		status['Status'] = 'Begin'
		self.emitEvent(status)
		path = self.track.location
		if self.track.isStream:
			print " Why here"
			status = self.getStatus()
			status['Status'] = 'Connecting'
			self.emitEvent(status)
			self.track.fetchStream()
			path = self.track.stream
		print "about to play",path
		_stream.open(cleanPath(path))
		self.opened = True
		return True
		'''
		#except:
			status = self.getStatus()
			status['Status'] = 'End'
			self.emitEvent(status)
		return False
		'''
	def run(self):
		global _stream
		try:
			if self.open():
				try:
					_stream.play()
					LXineObserver.run(self)
				except:
					self.die()
		except:
			# XXX DSM throw error here
			self.die()
			pass

	def pause(self):
		global _stream
		_stream.speed = 'XINE_SPEED_PAUSE'
		#_stream.setSpeed(0)
		LXineObserver.pause(self)
	
	def play(self):
		global _stream
		#_stream.speed = 'XINE_SPEED_FAST_2'
		_stream.speed = 'XINE_SPEED_NORMAL'
		#_stream.setSpeed(_currentPlayerSpeed)
		LXineObserver.play(self)

	def step(self):
		global _stream
		self.msleep(100)
		status = _stream.get_status()
		done = status!='XINE_STATUS_PLAY'
		return done

	def setVolume(self,volume):
		global _stream
		_stream.audio_volume = volume
	
	def setSpeed(self,speed):
		global _stream
		newSpeed = long(1000000L*(speed/100.0))
		print "newSpeed",newSpeed
		pyxine.libxine.xine_set_param(_stream.this,30,newSpeed)

	def setEQ(self,eq):
		try:
			if eq==None or not eq['On']:
				self.setBands([0,0,0,0,0,0,0,0,0,0])
			else:
				self.setBands(eq['Bands'])
		except: pass

	def setBands(self,bands):
		global _stream,_eqMultiplier,_eqOffset
		for i in xrange(0,10):
			pyxine.libxine.xine_set_param(_stream.this,18+i,_eqMultiplier*bands[i]+_eqOffset)

	def getPos(self):
		global _stream
		if self.opened:
			try:
				(currentPosition,currentTime,totalTime) = _stream.get_pos_length()
				if totalTime:
				#	currentTime = currentTime*self.track.totalTime/totalTime/1000
					totalTime = self.track.totalTime/1000.0
					currentTime = currentPosition*totalTime/100.0
				#print currentPosition,currentTime,totalTime
			except:
				currentTime = 0
				totalTime = 1
			return (currentTime*1000,totalTime*1000)
		else:
			return (0,100)

	def setPos(self,time):
		global _stream
		(currentPosition,currentTime,totalTime) = _stream.get_pos_length()
		time = time*totalTime*1000/self.track.totalTime
		_stream.play(start_time=(time/1000.0))

	def die(self):
		global _stream
		#print "Xine stream closing"
		if _stream:
			_stream.stop()
			_stream.close()
		#self.stream = None
	
	def getStatus(self):
		global _stream
		status = LXineObserver.getStatus(self)
		"""if status['Status']=='Playing' and self.track.isStream and not self.track.lsongs:
			if self.track.isPreview:
				status['Current Title'] = self.track.title
			elif self.track.remote:
				status['Current Title'] = self.track.title
			else:
				status['Current Title']= _stream.title
				if _stream.album: # do not overrride if "None"
					self.track.title = _stream.album
			try: self.track.bitRate = _stream.bitrate
			except: pass
			try:
				if self.track.genre==None:
					genre = _stream.genre
					if genre and len(genre)>0:
						#print "got stream genre",genre
						self.track.genre = guessGenre(genre)
			except: pass"""
		return status

#
# an object used to emit signals from within this module
# it also does some housecleaning for the status of individual tracks
#
class LXinePlayer(QObject):
	def __init__(self,parent):
		global _parent
		QObject.__init__(self)
		self.loadFromPrefs()
		self.xine()
		_parent=parent
	
	def loadFromPrefs(self):
		global _eqMultiplier,_eqOffset
		settings = LSettings.settings()
		self.setVolume(settings.get('Volume',50))
		_eqMultiplier = settings.get("EQ Multiplier",10)
		_eqOffset = settings.get("EQ Offset",0)
		self.setEQ(settings.get("EQ Settings",{'On': False, 'Preamp': 0, 'Bands': [0,0,0,0,0,0,0,0,0,0]}))

	def volume(self):
		global _currentPlayerVolume
		return _currentPlayerVolume
	
	def setVolume(self,volume,fromObject = None):
		global _currentPlayerVolume
		_currentPlayerVolume = volume
		#print "setting volume to ",volume
		LSettings.settings()['Volume'] = volume
		self.putCommand("volume")
		self.emit(PYSIGNAL("volumeChanged"),(volume,fromObject))

	def increaseVolume(self):
		self.setVolume(min(self.volume()+10,100))

	def decreaseVolume(self):
		self.setVolume(max(self.volume()-10,0))

	def setSpeed(self,speed,fromObject = None):
		global _currentPlayerSpeed
		_currentPlayerSpeed = speed
		#print "setting speed to ",speed
		LSettings.settings()['Speed'] = speed
		LXinePlayer.putCommand("speed")
		self.emit(PYSIGNAL("speedChanged"),(speed,fromObject))

	def setEQ(self,eq):
		global _currentEQ
		_currentEQ = eq
		self.putCommand('eq')
	'''
	def customEvent(self,e):
		global _playerStatus
		_playerStatus = e.status()
		#print "got event status",_playerStatus
		if _playerStatus:
			status = _playerStatus["Status"]
			track = _playerStatus['Track']
			if status=="Playing":
				if _playerStatus['isPlaying']:
					track.playStatus = 'playing'
				else:
					track.playStatus = 'paused'
			elif status=='End':
				#print "LXinePlayer: End",status
				track.playStatus = 'idle'
				if _playerStatus["Interrupted"]==False:
					track.playCount = track.playCount+1
					track.playDate = time.gmtime(time.time())
					if not track.isStream:
						playing = LXinePlayer.nextTrack()
			#elif status=='Begin':
			#	print "LXinePlayer: Beginning"
			self.emit(PYSIGNAL('status'),(_playerStatus,None))
	'''
	
	def playTrack(self,track,library=None,playlist=None):
		global _curPlayerThread
		global _currentPlayerTrack
		global _currentPlayerLibrary
		global _currentPlayerPlaylist
		global _canOverride
		global _played
		global _xine
		global _nextCDPlayer
		self.killCurrentPlayer()
		if track.kind=='CD Track':
			#print "setting player to",_nextCDPlayer
			_xine.config['input.cdda_device'] = _nextCDPlayer
		_currentPlayerTrack = track
		_currentPlayerLibrary = library
		_currentPlayerPlaylist = playlist
		#print "playing",track.title,"from playlist",playlist
		self.putCommand("none")
		_curPlayerThread = LXinePlayerThread(track)
		_curPlayerThread.start()
		_canOverride = False
		_played = True

	
	def playerStatus(self):
		global _playerStatus
		return _playerStatus
	
	def stop(self):
		pass
	

	def killCurrentPlayer(self):
		global _curPlayerThread,_altPlayerThread
		global _currentPlayerTrack, _stream
		if _curPlayerThread!=None:
			#print "killing current player"
			#traceback.print_stack()
			self.putCommand("stop")
			t = long(time.time())+5
			while t>=long(time.time()):
				if self.getResponse()=='stopped':
					break
				time.sleep(0.1)
			if _curPlayerThread!=None and not _curPlayerThread.wait(2000):
				print "LXinePlayer: thread is hung, terminating"
				_curPlayerThread.terminate()
				print "LXinePlayer: waiting for termination"
				try:
					if _curPlayerThread and _curPlayerThread.wait(2000):
						print "LXinePlayer: thread hopelessly hung"
				except: pass
				_curPlayerThread = None
				_altPlayerThread = None
				_stream = None
			else:
				pass
				#print "LXinePlayer: thread died cleanly"

	def killCurrentPlayerIfCD(self):
		global _currentPlayerTrack
		if _currentPlayerTrack and _currentPlayerTrack.kind == 'CD Track':
			self.killCurrentPlayer()
	

	def play(self):
		global _curPlayerThread
		#print "LXinePlayer:play"
		if _curPlayerThread:
			if not (self.isPlayingCDTrack() and Ripper.singleton().isRipping()):
				self.putCommand('play')
	
	
	def pause(self):
		global _curPlayerThread
		#print "LXinePlayer:pause"
		if _curPlayerThread:
			self.putCommand('pause')

	def seek(self,time):
		global _curPlayerThread,_seekTime
		#print "LXinePlayer:pause"
		if _curPlayerThread:
			_seekTime = time
			self.putCommand('seek')

	def getResponse(self):
		global _playerResponse
		global _playerMutex
		_playerMutex.lock()
		cmd = _playerResponse
		_playerMutex.unlock()
		return cmd
	

	def putCommand(self,cmd):
		global _playerCommand
		global _playerResponse
		global _playerMutex
		_playerMutex.lock()
		_playerCommand = cmd
		_playerResponse = ''
		_playerMutex.unlock()
	
	def xine(self):
		global _xine,_xineConfig
		if _xine == None:
			prefs = {'input.cdda_use_cddb':0}
			settings = LSettings.settings()
			memcpy = settings.get('Xine MemCpy',None)
			if memcpy: prefs['misc.memcpy_method'] = memcpy
			_xine = pyxine.Xine(prefs)
			settings['Xine MemCpy'] = _xine.config['misc.memcpy_method'].value
			_xineConfig = pyxine.config.XineConfig(_xine)
		return _xine

        def isPlayingCDTrack(self):
                global _currentPlayerTrack
                return _currentPlayerTrack and _currentPlayerTrack.kind=='CD Track'

	def killAll(self):
		global _xine,_altPlayerThread,_curPlayerThread,_played
		self.killCurrentPlayer()
		_altPlayerThread = None
		_curPlayerThread = None
	
	def setCDPlayer(self,player):
		global _nextCDPlayer,_prevCDPlayer
		print "setCDPlayer",player
		_prevCDPlayer = _nextCDPlayer
		_nextCDPlayer = player

